/*
 * Stopwatch.java
 *
 * Created on Dec 11, 2007 at 3:46:55 PM
 *
 * Copyright 2008 the Desert Labs Project (http://desertlabs.us)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package us.desertlabs;

import java.util.concurrent.TimeUnit;

/**
 * This class provides a very simple API to aid in performing timings.  It has
 * only 4 public methods: <CODE>start()</CODE>, <CODE>tick()</CODE>, <CODE>lap()</CODE>,
 * and <CODE>stop</CODE>. None of the methods require a parameter, as the current
 * <CODE>Thread</CODE> is always used to determine the scope of any given timing.
 *
 * @author Brandon Franklin
 */
public class Stopwatch {

    /** The singleton instance */
    private static final Stopwatch INSTANCE = new Stopwatch();

    /**
     * Returns the singleton instance.
     *
     * @return the singleton instance
     */
    public static Stopwatch getInstance() {
        return INSTANCE;
    }

    /** The <CODE>ThreadLocal</CODE> associating calling threads with their lap times */
    private final ThreadLocal<Long> lapTimes;

    /** The <CODE>ThreadLocal</CODE> associating calling threads with their start times */
    private final ThreadLocal<Long> startTimes;

    /**
     * Creates a new instance of <CODE>Stopwatch</CODE> and prepares it for use.
     */
    private Stopwatch() {
        startTimes = new ThreadLocal<Long>();
        lapTimes = new ThreadLocal<Long>();
    }

    /**
     * Returns the number of milliseconds that have elapsed since the last call to <CODE>lap()</CODE>
     * by this thread, or since the timing of the thread began if <CODE>lap()</CODE> has not been
     * called by this thread before, and continues timing the thread.  The <CODE>stop()</CODE> method
     * will still need to be called later.  The purpose of this method is to provide intermediate timings
     * of specific subsections of code.
     *
     * @return the number of milliseconds that have elapsed since the last call to <CODE>lap()</CODE>
     */
    public long lap() {
        if( lapTimes.get() != null ) {
            final long prevLapTime = lapTimes.get();
            final long nowTime = System.nanoTime();
            lapTimes.set( nowTime );
            final long newTime = nowTime - prevLapTime;
            return TimeUnit.NANOSECONDS.toMillis( newTime );
        }

        throw new IllegalStateException( "Cannot call lap without calling start from same thread first" );
    }

    /**
     * Begins timing the current thread.
     */
    public void start() {
        if( startTimes.get() == null ) {
            final long time = System.nanoTime();
            startTimes.set( time );
            lapTimes.set( time );
        } else {
            throw new IllegalStateException( "Start has already been called by this thread" );
        }
    }

    /**
     * Stops timing the current thread, and returns the number of milliseconds that have
     * elapsed since timing began.
     *
     * @return the number of milliseconds that have elapsed since timing began
     */
    public long stop() {
        if( startTimes.get() != null ) {
            final long startTime = startTimes.get();
            startTimes.remove();
            lapTimes.remove();
            return TimeUnit.NANOSECONDS.toMillis( System.nanoTime() - startTime );
        }

        throw new IllegalStateException( "Cannot call stop without calling start from same thread first" );
    }

    /**
     * Returns the number of milliseconds that have elapsed since timing of the thread began,
     * and continues timing the thread.  The <CODE>stop()</CODE> method will still need to
     * be called later.  The purpose of this method is to provide intermediate timings.
     *
     * @return the number of milliseconds that have elapsed since timing began
     */
    public long tick() {
        if( startTimes.get() != null ) {
            final long startTime = startTimes.get();
            return TimeUnit.NANOSECONDS.toMillis( System.nanoTime() - startTime );
        }

        throw new IllegalStateException( "Cannot call tick without calling start from same thread first" );
    }
}
